package jlink

import (
	"encoding/binary"
	"errors"
	"fmt"
)

const (
	JKCMD_GETID = 0x0E01
	JKCMD_PARAM = 0x0D02
	JKCMD_ERASE = 0x0C03
	JKCMD_READ  = 0x0B04
	JKCMD_PROG  = 0x0A05
)

/*
Nor:
	page < sector < block

	   Read : byte
	Program : page
	  Erase : sector, 32k block, 64k block


Nand:
	page < block

	   Read : page
	Program : page
	  Erase : block


*/

type Sflash struct {
	jk     *Jlink
	schip  uint64 /* chip, total bytes */
	sblock int    /* block, min erase unit, bytes */
	spage  int    /* page, min program unit, bytes */
}

func dcc_sendReq(jk *Jlink, cmd uint16, args []byte) error {
	var temp uint32
	var tary [4]byte

	// zero args
	if args == nil || len(args) == 0 {
		temp = uint32(cmd) << 16
		binary.LittleEndian.PutUint32(tary[0:], temp)
		return jk.WriteDCC(tary[:], 10)
	}

	//
	tlen := len(args)
	if tlen&0x3 != 0 {
		args = append(args, 0, 0, 0)
		tlen = tlen + 4 - (tlen & 0x3)
	}

	//
	temp = uint32(cmd)<<16 | uint32(tlen>>2)
	binary.LittleEndian.PutUint32(tary[0:], temp)
	err := jk.WriteDCC(tary[:], 10)
	if err != nil {
		return err
	}

	//
	return jk.WriteDCC(args[0:tlen], 10)
}

/*
tms : wait, timeout, ms
rcmd, rlen : 期望的响应 cmd，len ;
如果不满足， 返回 eret 非空， 表示确切的错误。
*/
func dcc_recvRsp(jk *Jlink, tms uint32, rcmd uint16, rlen int) (args []byte, eret error) {
	var rsp []byte
	var err error

	// first uint32
	rsp, err = jk.ReadDCC(4, tms)
	if err != nil {
		// 通常是超时错误, jlink 有可能断开链接了。
		return nil, err
	}

	// cmd，len  from uint32
	hdr := binary.LittleEndian.Uint32(rsp[0:])
	tlen := (hdr & 0xFFFF) << 2
	tcmd := uint16(hdr >> 16)

	if tlen == 0 {
		if tcmd == rcmd && rlen == 0 {
			return nil, nil
		} else {
			return nil, errors.New(fmt.Sprintf("exp cmd neq, cmd=%X, len=%v", tcmd, rlen))
		}
	}

	//
	args, eret = jk.ReadDCC(tlen, 10)
	if eret != nil {
		return nil, eret
	}

	//
	if tcmd == rcmd && tlen == uint32(rlen) {
		return args, nil
	} else {
		return nil, errors.New(fmt.Sprint("exp cmd neq and tlen=", tlen))
	}

}

func NewFlash(jk *Jlink) (*Sflash, error) {
	var err error
	var sfh Sflash
	var tary [8]byte

	/* probe */
	// send request
	err = dcc_sendReq(jk, JKCMD_GETID, nil)
	if err != nil {
		return nil, err
	}

	// recv resp
	rsp, err := dcc_recvRsp(jk, 100, JKCMD_GETID, 4)
	if err != nil {
		return nil, err
	}

	tid := binary.LittleEndian.Uint32(rsp[0:])
	fmt.Printf("probe, flash id, 0x%x\n", tid)

	if tid == 0xef401800 {
		/* winbond, 25Q128JV */
		sfh.jk = jk
		sfh.schip = 16777216 /* 16M bytes */
		sfh.sblock = 4096    /* 4k block */
		sfh.spage = 256

	} else if tid == 0xc84015c8 {

		/* Gigadevice, 25Q16ESIG */
		sfh.jk = jk
		sfh.schip = 2097152 /* 2M bytes */
		sfh.sblock = 4096   /* 4k block */
		sfh.spage = 256

	} else if tid == 0xc84016c8  {

		/* Gigadevice, 25Q32CS */
		sfh.jk = jk
		sfh.schip = 4194304 /* 4M bytes */
		sfh.sblock = 4096   /* 4k block */
		sfh.spage = 256

	} else if tid == 0xc84018c8  {

		/* Gigadevice, 25Q128CS */
		sfh.jk = jk
		sfh.schip = 16777216 /* 16M bytes, 128M bits */
		sfh.sblock = 4096   /* 4k block */
		sfh.spage = 256

	} else {
		return nil, errors.New(fmt.Sprintf("unknown flash id, 0x%X", tid))
	}

	/**/
	binary.LittleEndian.PutUint32(tary[0:], uint32(sfh.spage))
	binary.LittleEndian.PutUint32(tary[4:], uint32(sfh.sblock))

	err = dcc_sendReq(jk, JKCMD_PARAM, tary[0:8])
	if err != nil {
		return nil, err
	}

	/* resp */
	_, err = dcc_recvRsp(jk, 20, JKCMD_PARAM, 0)
	if err != nil {
		return nil, err
	}

	/**/
	return &sfh, nil

}

func (sfh *Sflash) ReadAt(p []byte, off int64) (n int, err error) {

	var sadr uint32 /* start page  */
	var send uint32 /* end page   */
	var cntp int    /* page counter */
	var skip int    /* first page, skip bytes */
	var tail int    /* tail page, remain bytes */
	var ofs int
	var temp uint64
	var tary [8]byte
	var eret error

	/**/
	if (off < 0) || len(p) == 0 || uint64(off) >= sfh.schip {
		return 0, errors.New("args error")
	}

	/* check range */
	sadr = uint32(uint64(off) / uint64(sfh.spage))
	skip = int(uint64(off) - uint64(sadr*uint32(sfh.spage)))

	temp = uint64(off) + uint64(len(p))
	if temp >= sfh.schip {
		temp = sfh.schip
	}

	send = uint32((temp - 1) / uint64(sfh.spage))
	cntp = int(send - sadr + 1)
	tail = sfh.spage - int((send+1)*uint32(sfh.spage)-uint32(temp))

	/**/
	fmt.Printf("start read: %v, %v, %v, %v, %v\n", sadr, send, cntp, skip, tail)

	/* read start */
	binary.LittleEndian.PutUint32(tary[0:], uint32(sadr))
	binary.LittleEndian.PutUint32(tary[4:], uint32(cntp))
	eret = dcc_sendReq(sfh.jk, JKCMD_READ, tary[0:8])
	if eret != nil {
		return 0, eret
	}

	/* first page */
	rsp, eret := dcc_recvRsp(sfh.jk, 20, JKCMD_READ, sfh.spage)
	if eret != nil {
		return 0, eret
	}

	/**/
	if cntp == 1 {
		fmt.Printf("#\n")
		copy(p[0:], rsp[skip:tail])
		return (tail - skip), nil
	}

	fmt.Printf("#")

	// fmt.Println(hex.Dump(rsp[:]))
	copy(p[0:], rsp[skip:])
	ofs = sfh.spage - skip

	/* middle page */
	for i := 0; i < (cntp - 2); i++ {
		rsp, eret = dcc_recvRsp(sfh.jk, 20, JKCMD_READ, sfh.spage)
		if eret != nil {
			return 0, eret
		}

		copy(p[ofs:], rsp)
		ofs += sfh.spage

		fmt.Printf("#")
	}

	/* last page */
	rsp, eret = dcc_recvRsp(sfh.jk, 20, JKCMD_READ, sfh.spage)
	if eret != nil {
		return 0, eret
	}

	fmt.Printf("#\n")

	// fmt.Println(hex.Dump(rsp[:]))
	copy(p[ofs:], rsp[0:tail])
	ofs += tail
	return ofs, nil
}

func (sfh *Sflash) WriteAt(p []byte, off int64) (n int, err error) {

	var sblk uint32
	var eblk uint32
	var cntb int /* block counter */
	// var temp uint64
	var tary [8]byte

	var eret error

	//
	if (off < 0) || len(p) == 0 || uint64(off) >= sfh.schip {
		return 0, errors.New("args error")
	}

	// erase
	sblk = uint32(uint64(off) / uint64(sfh.sblock))
	eblk = uint32((uint64(off) + uint64(len(p)-1)) / uint64(sfh.sblock))
	cntb = int(eblk-sblk) + 1

	//
	fmt.Printf("erase, start : %v, cnt : %v\n", sblk, cntb)

	/* erase start */
	binary.LittleEndian.PutUint32(tary[0:], uint32(sblk))
	binary.LittleEndian.PutUint32(tary[4:], uint32(cntb))
	eret = dcc_sendReq(sfh.jk, JKCMD_ERASE, tary[0:8])
	if eret != nil {
		return 0, eret
	}

	for i := 0; i < cntb; i++ {

		/**/
		_, eret = dcc_recvRsp(sfh.jk, 100, JKCMD_ERASE, 0)
		if eret != nil {
			return 0, eret
		}

		fmt.Printf("#")
	}

	fmt.Println()

	/* align to page, need padding? */
	prefix := int(uint32(off) % uint32(sfh.spage))
	suffix := int(uint32(off+int64(len(p))) % uint32(sfh.spage))
	suffix = (sfh.spage - suffix) % sfh.spage
	total := prefix + len(p) + suffix
	sttp := (int(off) - prefix) / sfh.spage
	cntp := total / sfh.spage
	ary := make([]byte, total)
	copy(ary[prefix:], p[0:])

	//
	fmt.Printf("prog, start : %v, cnt : %v\n", sttp, cntp)

	/* prog begin */
	binary.LittleEndian.PutUint32(tary[0:], uint32(sttp))
	binary.LittleEndian.PutUint32(tary[4:], uint32(cntp))
	eret = dcc_sendReq(sfh.jk, JKCMD_PROG, tary[0:8])
	if eret != nil {
		return 0, eret
	}

	_, eret = dcc_recvRsp(sfh.jk, 20, JKCMD_PROG, 0)
	if eret != nil {
		fmt.Println("prog, begin, ", eret)
		return 0, eret
	}

	fmt.Printf("#")

	/* prog pages */
	for i := 0; i < cntp; i++ {
		eret = dcc_sendReq(sfh.jk, JKCMD_PROG, ary[i*sfh.spage:(i+1)*sfh.spage])
		if eret != nil {
			return 0, eret
		}

		/**/
		// fmt.Printf("prog page: %v, %x\n", i, i*sfh.spage)
		// fmt.Println(hex.Dump(ary[i*sfh.spage : (i+1)*sfh.spage]))

		_, eret = dcc_recvRsp(sfh.jk, 100, JKCMD_PROG, 0)
		if eret != nil {
			fmt.Printf("prog, pages %v, %v\n", i, eret)
			return 0, eret
		}

		fmt.Printf("#")
	}

	fmt.Printf("\n")
	return len(p), nil
}
